#!/usr/bin/env python
# -*- Mode: Python -*-
# vi:si:et:sw=4:sts=4:ts=4
#
# Test:  try to handle LOS , ie test the event handling stuff
# 
# 1) Modify one of the inputs, to use a streaming input
# 2) add signal handling to handle LOS and switch to a valid input
# Test this by killing off streaming input (shell kill)
#
# Learned:
#

import time
import pygtk
import threading
pygtk.require('2.0')

import sys

import gobject
gobject.threads_init()

import pygst
pygst.require('0.10')
import gst
import gst.interfaces
import gtk
gtk.gdk.threads_init()

class SwitchTest:
    def __init__(self, videowidget):
        self.buffers = []
        self.playing = False
        self.myvar = True
        self.LOS = False
        self.lock = threading.Lock()
       
        
        caps = gst.Caps("video/x-theora")
     
        self.pipeline2 = gst.Pipeline("pipeline2")

        uri2 = gst.element_factory_make("souphttpsrc","uri2")
        uri2.set_property("location","http://localhost:8000/p1.ogg")
        #uri2.set_property("is-live",True)
        q2 = gst.element_factory_make("queue","q2")
        q3 = gst.element_factory_make("queue","q3")
        #ogg = gst.element_factory_make("oggdemux","ogg")
        #filter2 = gst.element_factory_make("theoraenc","filter2")
        #filter2.set_property("bitrate",400)
        sink = gst.element_factory_make("appsink","sink")
        sink.set_property("caps",caps)
        sink.set_property('emit-signals', True)
        sink.set_property('sync', False)


        self.pipeline2.add(uri2,q2,sink)

        #Link stuff up
        uri2.link(q2)
        q2.link(sink)
        #ogg.connect("pad-added",self.onPad,sink)
        
        self.pipeline2.set_state(gst.STATE_PAUSED)

        #Now set up the master pipeline
        self.pipeline = gst.Pipeline("pipeline")
        uri = gst.element_factory_make("videotestsrc","uri")
        uri.set_property("pattern",1)
        source = gst.element_factory_make("appsrc","source")
        #source.set_property("is-live",True)
        tee = gst.element_factory_make("tee","tee")
        filesink = gst.element_factory_make("filesink","filesink")
        filesink.set_property("location","/tmp/out.ogg")
        om = gst.element_factory_make("oggdemux","om")
        theoradec = gst.element_factory_make("theoradec","theoradec")
        fcolor = gst.element_factory_make("ffmpegcolorspace","fcolor")
        #caps = gst.Caps("video/x-raw-yuv,width=320,height=240,framerate=30/1")
        #fcolor.set_property("caps",caps)
        vscale = gst.element_factory_make("videoscale","vscale")
        blah = gst.element_factory_make("videotestsrc","blah")
        blah.set_property("pattern",0)
        s = gst.element_factory_make("input-selector","s")
        blah2 = gst.element_factory_make("autovideosink","blah2")
        q1 = gst.element_factory_make("queue","q1")
        q4 = gst.element_factory_make("queue","q4")
        q5 = gst.element_factory_make("queue","q5")


        self.pipeline.add(uri,source,tee,filesink,om,theoradec,fcolor,vscale,blah,s,blah2,q1,q3,q4,q5)


        #
        #link stuff up
        #
        # link up s.sink0
        #
        pad = s.get_pad('sink0')
        srcpad = uri.get_compatible_pad(pad, pad.get_caps())
        srcpad.link(pad)
        #
        # link up s.sink1
        #
        source.link(q1)
        q1.link(tee)
        tee.link(filesink)
        tee.link(om)
        om.connect("pad-added",self.onPad,theoradec)
        theoradec.link(fcolor)
        fcolor.link(q3)
        q3.link(vscale)
        pad = s.get_pad('sink1')
        srcpad = vscale.get_compatible_pad(pad, pad.get_caps())
        srcpad.link(pad)
        #
        # link up s.sink2
        #
        pad = s.get_pad('sink2')
        srcpad = blah.get_compatible_pad(pad, pad.get_caps())
        srcpad.link(pad)
        #
        # and now the rest
        #
        s.link(q4)
        q4.link(q5)
        q5.link(blah2)

    
        self.videowidget = videowidget

        source = self.pipeline.get_by_name("source")
        #source.connect('need-data', self.source_pull_buffer)
        #source.connect('push-buffer', self.source_pull_buffer)
        self.source = source

        # attach some of the call backs now that both pipelines are up
        sink.connect('new-preroll', self.sink_new_preroll, self.pipeline)
        sink.connect('new-buffer', self.sink_new_buffer, self.pipeline)
        sink.connect('eos', self.eos, self.pipeline2, self.pipeline)

        self.pipeline2.set_state(gst.STATE_PLAYING)

        print "sink eos? ", sink.get_property("eos")

        bus = self.pipeline.get_bus()
        bus.enable_sync_message_emission()
        bus.add_signal_watch()
        bus.connect('sync-message::element', self.on_sync_message)
        bus.connect('message', self.on_message)


    def on_sync_message(self, bus, message):
        if message.structure is None:
            return
        if message.structure.get_name() == 'prepare-xwindow-id':
            # Sync with the X server before giving the X-id to the sink
            gtk.gdk.threads_enter()
            gtk.gdk.display_get_default().sync()
            self.videowidget.set_sink(message.src)
            message.src.set_property('force-aspect-ratio', True)
            gtk.gdk.threads_leave()
            
    def onPad(self,element, pad,target):
        print "CALLED onPAD"
        #sinkpad = target.get_compatible_pad(pad, pad.get_caps())
        #print sinkpad
        #pad.link(sinkpad)
        caps = pad.get_caps()
        name = caps[0].get_name()
        #for now only link the video pad from uridecode
        if name == 'video/x-theora':
            tpad = target.get_pad('sink')
            if not tpad.is_linked():
                pad.link(tpad)
                print 'on_pad_added:', name
            else:
                print "already linked"
        else:
            print "did nothing"

        return True


    def on_eos(self):
        print "more eos"
        return True

    def on_message(self, bus, message):
        t = message.type
        if t == gst.MESSAGE_ERROR:
            err, debug = message.parse_error()
            print "Error: %s" % err, debug
            if self.on_eos:
                self.on_eos()
            self.playing = False
        elif t == gst.MESSAGE_EOS:
            if self.on_eos:
                self.on_eos()
            self.playing = False

    def play(self):
        self.playing = True
        gst.info("playing player")
        self.pipeline.set_state(gst.STATE_PLAYING)
        
    def stop(self):
        self.pipeline.set_state(gst.STATE_NULL)
        gst.info("stopped player")
        self.playing = False

    def get_state(self, timeout=1):
        return self.pipeline.get_state(timeout=timeout)

    def is_playing(self):
        return self.playing
    
    def switch(self, padname):
        switch = self.pipeline.get_by_name('s')
        stop_time = switch.emit('block')
        newpad = switch.get_static_pad(padname)
        start_time = newpad.get_property('running-time')
    
        gst.warning('stop time = %d' % (stop_time,))
        gst.warning('stop time = %s' % (gst.TIME_ARGS(stop_time),))

        gst.warning('start time = %d' % (start_time,))
        gst.warning('start time = %s' % (gst.TIME_ARGS(start_time),))

        gst.warning('switching from %r to %r'
                    % (switch.get_property('active-pad'), padname))
        switch.emit('switch', newpad, stop_time, start_time)

    def sink_new_preroll(self, sink, pipe):
         print "Prerolling sink"
         buf = sink.emit('pull-preroll')
         #self.lock.acquire()
         #self.buffers.append(buf)
         #self.lock.release()
         print "PREROLL BUFFERS: ", len(self.buffers)
         #self.source.emit('push-buffer',buf)

         mysrc = pipe.get_by_name("source")
         mysrc.emit('push-buffer', buf)

         return True

    def sink_new_buffer(self, sink, pipe):
         buf = sink.emit('pull-buffer')
         #self.lock.acquire()
         #self.buffers.append(buf)
         #self.lock.release()
         #print "NEW BUFFER BUFFERS: ", len(self.buffers)
         #self.source.emit('push-buffer',buf)

         mysrc = pipe.get_by_name("source")
         mysrc.emit('push-buffer', buf)

         return True

    def eos(self, sink, pipeline, pipeline0):
        print "EOS on sink"
        self.LOS = True


        #switch to static the active input-selector pad if the active one is the one we
        #just had an LOS on 
        s = pipeline0.get_by_name('s')
        activepad = s.get_property("active-pad")
        sink1 = s.get_pad('sink1')
        if activepad == sink1:
            self.switch('sink0')

        return True

    def eos_fix(junk,self):
        self.pipeline2.set_state(gst.STATE_READY)

        uri2 = self.pipeline2.get_by_name("uri2")
        if uri2:
            uri2.set_property("location","http://localhost:8000/p2.ogg")
        else:
            print "could not find element uri2"

        source = self.pipeline.get_by_name("source")
        #source.set_state(gst.STATE_PAUSED)
        

        #
        #Reset the demuxer and decoders since they seem to get stuck on restart
        #
        theoradec = self.pipeline.get_by_name("theoradec")
        #theoradec.set_state(gst.STATE_READY)
        om = self.pipeline.get_by_name("om")
        #om.set_state(gst.STATE_READY)

        print "unlinking om: ", om.unlink(theoradec)
        
        for p in theoradec.pads():
            print "start pad"
            print "direction ", p.get_direction()
            print "linked? ", p.is_linked()
            print "active? ", p.is_active()
            print "blocked? ", p.is_blocked()
            print "end pad"

        theoradec.sync_state_with_parent()
        om.sync_state_with_parent()
        source.sync_state_with_parent()
        
        self.pipeline2.set_state(gst.STATE_PLAYING)

        print "bye"

        return True

    def source_pull_buffer(self, source, length):
         #print "XXXXXPULLING BUFER"
         if self.buffers:
             self.lock.acquire()
             buf = self.buffers.pop(0)
             self.lock.release()
             source.emit('push-buffer', buf)
         return True


class VideoWidget(gtk.DrawingArea):
    def __init__(self):
        gtk.DrawingArea.__init__(self)
        self.imagesink = None
        self.unset_flags(gtk.DOUBLE_BUFFERED)

    def do_expose_event(self, event):
        if self.imagesink:
            self.imagesink.expose()
            return False
        else:
            return True

    def set_sink(self, sink):
        assert self.window.xid
        self.imagesink = sink
        self.imagesink.set_xwindow_id(self.window.xid)

class SwitchWindow(gtk.Window):
    UPDATE_INTERVAL = 500
    def __init__(self):
        gtk.Window.__init__(self)
        self.set_default_size(410, 325)

        self.create_ui()
        self.player = SwitchTest(self.videowidget)
        self.populate_combobox()

        self.update_id = -1
        self.changed_id = -1
        self.seek_timeout_id = -1

        self.p_position = gst.CLOCK_TIME_NONE
        self.p_duration = gst.CLOCK_TIME_NONE

        def on_delete_event():
            self.player.stop()
            gtk.main_quit()
        self.connect('delete-event', lambda *x: on_delete_event())

    def load_file(self, location):
        self.player.set_location(location)
                                  
    def play(self):
        self.player.play()
        
    def populate_combobox(self):
        switch = self.player.pipeline.get_by_name('s')
        for i, pad in enumerate([p for p in switch.pads()
                                 if p.get_direction() == gst.PAD_SINK]):
            self.combobox.append_text(pad.get_name())
            if switch.get_property('active-pad') == pad.get_name():
                self.combobox.set_active(i)
        if self.combobox.get_active() == -1:
            self.combobox.set_active(0)

    def combobox_changed(self):
        model = self.combobox.get_model()
        row = model[self.combobox.get_active()]
        padname, = row
        self.player.switch(padname)

    def button_callback(self, widget,button):
        if self.player.LOS:
            print "LOS!", len(self.player.buffers)
            self.player.eos_fix(self.player)

    def enter_callback(self, widget, entry):
        entry_text = entry.get_text()
        print "Entry contents: %s\n" % entry_text

    def create_ui(self):
        vbox = gtk.VBox()
        self.add(vbox)

        self.videowidget = VideoWidget()
        vbox.pack_start(self.videowidget)
        
        hbox = gtk.HBox()
        vbox.pack_start(hbox, fill=False, expand=False)
        
        self.combobox = combobox = gtk.combo_box_new_text()
        combobox.show()
        hbox.pack_start(combobox)

        self.combobox.connect('changed',
                              lambda *x: self.combobox_changed())

        self.videowidget.connect_after('realize',
                                       lambda *x: self.play())

        entry = gtk.Entry()
        entry.set_max_length(50)
        entry.connect("activate", self.enter_callback, entry)
        entry.set_text("hello")
        entry.insert_text(" world", len(entry.get_text()))
        entry.select_region(0, len(entry.get_text()))
        hbox.pack_start(entry)
        entry.show()

        button = gtk.Button("LOS reset")
        button.connect("clicked",self.button_callback,button)
        hbox.pack_start(button)
        button.show()


def main(args):
    def usage():
        sys.stderr.write("usage: %s\n" % args[0])
        return 1

    # Need to register our derived widget types for implicit event
    # handlers to get called.
    gobject.type_register(SwitchWindow)
    gobject.type_register(VideoWidget)

    if len(args) != 1:
        return usage()

    w = SwitchWindow()
    w.show_all()
    gtk.main()
    return 0

if __name__ == '__main__':
    sys.exit(main(sys.argv))



